<?php


function dump($var)
{
	var_dump($var);
	echo "<br />";
}


define("MOCK_ANY",0);
define("MOCK_EQUALS",1);
define("MOCK_ATLEAST",2);
define("MOCK_TIMES",3);

class MockedParam
{
	public $params = array();
	
	public function __construct(array $params)
	{
		$this->params = $params;
	}
	
	public function __toString()
	{
		$output = array();
		foreach($this->params as $param)
		{
			ob_start();
			var_dump($param);
			$output[] = ob_get_contents();
			ob_end_clean();
		}
		return implode(", ",$output);
	}
	
}

class MockedMethod
{
	public $methodName;
	public $expected = array();
	public $actual = array();
	public $return;
	public $multiplicity = 0;
	public $multiplicity_var = 0;
	public $params;
	
	public function __construct($methodName,$params)
	{
		$this->methodName = $methodName;
		$this->params = $params;
	}
}

class MockedClass
{
	public $className = "";
	public $replay = false;
	public $methods = array();

	public function __construct($className)
	{
		$this->className = $className;
	}
}

class Mock
{
	private static $mockingList = Array();

	private static function generateMethodCode($methodName,array $stringParams)
	{
		$methodCode = "\$params = array();";
		foreach($stringParams as $stringParam)
		{
			$methodCode .= "\$params[] = $stringParam;";
		}
		return $methodCode .= "return Mock::mockRefrenceCount(\$this->mockId,\"$methodName\",\$params);";
	}
	
	private static function generateParameterString(array $params)
	{
		$stringParams = array();
		foreach($params as $param)
		{
			$stringParams[] = "\$".$param->getName();
		}
		return $stringParams;
	}
	
	public static function createMock($className)
	{
		Mock::$mockingList[] = new MockedClass($className);

		$clazz = new $className();
		$mockId = $clazz->mockId = end(array_keys(Mock::$mockingList));
		dump($mockId);

		$r_class = new ReflectionClass($className);
		
		$r_methods = $r_class->getMethods();
		foreach($r_methods as $method)
		{
			$methodName = $method->getName();		// BUG REGARDING UP CASE TO LOWER CASE COMPAIRSON
			dump($methodName);

			$methodArgs = Mock::generateParameterString($method->getParameters());
			dump($methodArgs);
			
			$methodCode = Mock::generateMethodCode($methodName,$methodArgs);
			dump($methodCode);

			Mock::$mockingList[$mockId]->methods[$methodName] = new MockedMethod($methodName,$methodArgs);

			runkit_method_redefine($className,$methodName,implode(",",$methodArgs),$methodCode);
		}

		return $clazz;
	}

	public static function mockRefrenceCount($mock,$method,$params)
	{
		dump("Called by:".$mock." Method: ".$method);

		$caller = &Mock::$mockingList[$mock]->methods[$method];
		if(Mock::$mockingList[$mock]->replay == false)
		{
			$caller->expected[] = new MockedParam($params);
		} else {
			$caller->actual[] = new MockedParam($params);
			if($caller->multiplicity == MOCK_EQUALS && sizeof($caller->expected)<sizeof($caller->actual))
			{
				throw new Exception("Unexpected call to:".Mock::$mockingList[$mock]->className."->".$method."(".implode(",",$caller->params).") : Expected: ".$caller->expected." Actual: ".$caller->actual);
			}
			
			
			$e = end($caller->expected); 
			$a = end($caller->actual);		
			dump($e);
			dump($a);
			
			if($e != $a)
			{
				$paramdiff = array();
				for($i=0;$i<sizeof($a->params);$i++)
				{
					if($e->params[$i] != $a->params[$i])
					{
						$paramdiff[] = "Parameter ".$caller->params[$i].": ".$e->params[$i]." != ".$a->params[$i]."\n";
					}
				}
				
				throw new Exception("Call to: ".Mock::$mockingList[$mock]->className."->".$method."(".implode(",",$caller->params).") with bad parameters: ".implode("",$paramdiff));
			}
		}

		return $caller->return;
	}

	public static function mockReplay($mock)
	{
		Mock::$mockingList[$mock->mockId]->replay = true;
	}

	public static function mockReturn($mock,$method,$return)
	{
		Mock::$mockingList[$mock->mockId]->methods[$method]->return = $return;
	}

	public static function mockRule($mock, $method, $mode, $var)
	{
		Mock::$mockingList[$mock->mockId]->methods[$method]->multiplicity = $mode;
		Mock::$mockingList[$mock->mockId]->methods[$method]->multiplicity_var = $var;
	}

	public static function mockVerify($mock)
	{
		dump("Sizeof: ".sizeof(Mock::$mockingList[$mock->mockId]->methods));
		foreach(Mock::$mockingList[$mock->mockId]->methods as $method)
		{
			dump("Method: ".$method->methodName);
			switch($method->multiplicity)
			{
				case MOCK_EQUALS:
					{
						dump("MOCK_EQUALS Expected: ".sizeof($method->expected)." : Actual: ".sizeof($method->actual));
							
						if(sizeof($method->expected) != sizeof($method->actual))
						{
							throw new Exception("Too few calls to:".Mock::$mockingList[$mock->mockId]->className."->".$method->methodName."(".$method->params.") : Expected: ".sizeof($method->expected)." Actual: ".sizeof($method->actual));
						}
						break;
					}
				case MOCK_ATLEAST:
					{
						dump("MOCK_ATLEAST Times: ".$method->multiplicity_var." : Actual: ".sizeof($method->actual));
							
						if($method->multiplicity_var >= sizeof($method->actual))
						{
							throw new Exception("Too few calls to:".Mock::$mockingList[$mock->mockId]->className."->".$method->methodName."(".$method->params.") : Expected greater or equal to: ".$method->multiplicity_var." Actual: ".sizeof($method->actual));
						}
						break;
					}
				case MOCK_TIMES:
					{
						dump("MOCK_TIMES Times: ".$method->multiplicity_var." : Actual: ".sizeof($method->actual));
							
						if($method->multiplicity_var != sizeof($method->actual))
						{
							throw new Exception("Incorrect Number of calls:".Mock::$mockingList[$mock->mockId]->className."->".$method->methodName."(".$method->params.") : Expected equal to: ".$method->multiplicity_var." Actual: ".sizeof($method->actual));
						}
						break;
					}
				default:
				case MOCK_ANY:
					dump("MOCK_ANY");
			}
		}
	}
}




?>
